/**
 * @author David Young
 */
package csab.core;

import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Font;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.xml.*;

import csab.core.utility.SpriteFactory;

import java.util.HashMap;

/**
 * Manages, loads, and distributes resources amongst
 * the code components that require them.
 * @author David Young
 */
public class ResourceManager {
	//Duration per frame of animation
	private static final String ANIM_DURATION_ATTR = "duration";
	//The name of each XML element in the animations category
	private static final String ANIM_ELEMENTNAME = "animation";
	
	//The ending x frame of an animation
	private static final String ANIM_FFRAMESX_ATTR = "f_xframe";
	
	//The ending y frame of the animation
	private static final String ANIM_FFRAMESY_ATTR = "f_yframe";
	//The starting x frame of an animation
	private static final String ANIM_SFRAMESX_ATTR = "s_xframe";
	//The starting y frame of the animation
	private static final String ANIM_SFRAMESY_ATTR = "s_yframe";
	//Reference to the spritesheet this animation is pulled from. If this is
	//empty then the REFERENCE_ATTR will be polled for its value and then a
	//unique image instance will be attached to this animation. Not
	//recommended (stick to sprite sheets).
	private static final String ANIM_SPRSHEETNAME_ATTR = "spritesheet";
	//The name of each XML element in the font category
	private static final String FONT_ELEMENTNAME = "font";
	//Font file (for angelcode) OR the ttf file (for unicode)
	private static final String FONT_FFILE_ATTR = "fontfile";
	//The type of font pointed to by the resource reference
	private static final String FONT_TYPE_ATTR = "type";
	
	//The name of each XML element in the images category
	private static final String IMAGE_ELEMENTNAME = "image";
	//Y location of the image on the sprite sheet (if used)
	private static final String IMAGE_LOCX_ATTR = "locx";
	//X location of image on the sprite sheet (if used)
	private static final String IMAGE_LOCY_ATTR = "locy";
	
	//Reference to a spritesheet (used instead of location if specified)
	//though it defaults to location if the spritesheet cannot be located.
	private static final String IMAGE_SPRSHEETNAME_ATTR = "spritesheet";
	//The name of the sub-element of one of the 5 primary nodes
	private static final String NAME_ATTR = "name";
	private static final String NULL_ATTR = "NULL";
	//The reference to the resource
	private static final String REFERENCE_ATTR = "location";
	
	//The name of each XML element in the sound category
	private static final String SOUND_ELEMENTNAME = "sound";
	//The name of each XML element in the sprite category
	private static final String SPRITE_ELEMENTNAME = "sprite";
	//The type of sprite
	private static final String SPRITE_HEALTH_ATTR = "health";
	
	//Is the sprite killable?
	private static final String SPRITE_KILLABLE_ATTR = "killable";
	
	//The in-game name of this sprite
	private static final String SPRITE_SPRNAME_ATTR = "spritename";
	//The sprite type
	private static final String SPRITE_TYPE_ATTR = "type";
	//Name of the XML element in the spritesheets category
	private static final String SS_ELEMENTNAME = "spritesheet";
	//Height of each spritesheet cell
	private static final String SS_FRAMEH_ATTR = "frameheight";
	
	//Width of each spritesheet cell
	private static final String SS_FRAMEW_ATTR = "framewidth";
	//Orientation of the spritesheet
	private static final String SS_HORIZONTAL_ATTR = "horizontal";

	private static HashMap<String, ResourceManager> mgrmap;
	
	private String rscxmlname;
	
	private HashMap<String, ManagerNode> animxmlmap;
	private HashMap<String, ManagerNode> fontxmlmap;
	private HashMap<String, ManagerNode> imagexmlmap;
	private HashMap<String, ManagerNode> soundxmlmap;
	private HashMap<String, ManagerNode> spritexmlmap;
	private HashMap<String, ManagerNode> ssxmlmap;
	
	static {
		mgrmap = new HashMap<String, ResourceManager>(10);
	}
	
	public static ResourceManager getManager(String xmlpath) throws SlickException
	{
		if(!mgrmap.containsKey(xmlpath))
		{
			mgrmap.put(xmlpath, new ResourceManager(xmlpath));
		}
		return mgrmap.get(xmlpath);
	}
	
	/**
	 * Construct a resource manager for the specified XML file. Be careful
	 * to not duplicate these references unintentionally.
	 * @param resourceXML The path/name of the XML file to create a
	 * ResourceManager from.
	 * @throws SlickException 
	 */
	private ResourceManager(String resourceXML) throws SlickException {
		XMLParser parser = new XMLParser();
		XMLElement base = null;
		rscxmlname = resourceXML;
		try {
			base = parser.parse(resourceXML);
		} catch (SlickException e) {
			Log.error("Slick Exception(" + 
					e.getMessage() + 
					") Could not find the " + resourceXML + " file or the file is malformatted.");
			throw e;
		}
		
		try {
			initXML(base.getChildren());
		} catch(SlickException e) {
			Log.error("Slick Exception(" + 
					e.getMessage() + 
					") An element is malformatted.");
			throw e;
		}
	}
	
	/**
	 * This is unnecessary and is only added for completeness.
	 * SpriteFactory objects can be garbage collected without
	 * worry of memory leaks. This may free some memory however if
	 * you find that you aren't using this specific type of Sprite.
	 * @param sprite The identifier of the SpriteFactory to free.
	 * @see csab.core.utility.SpriteFactory
	 */
	public void freeSpriteFactory(String sprite)
	{
		if(spritexmlmap.containsKey(sprite))
		{
			ManagerNode node = spritexmlmap.get(sprite);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Animation does not exist!");
	}
	
	/**
	 * Free a Font resource (if its' reference count is less
	 * than 0) otherwise just decrement the reference count.
	 * @param font The identifier of the Font to free.
	 * @see org.newdawn.slick.Font
	 * @throws SlickException
	 */
	public void freeFont(String font) throws SlickException
	{
		if(fontxmlmap.containsKey(font))
		{
			ManagerNode node = fontxmlmap.get(font);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				FontAttribs attr = (FontAttribs)node.attributes;
				if(attr.type.compareToIgnoreCase("angelcode") == 0){
					AngelCodeFont fnt = (AngelCodeFont)node.attributes.resource;
					fnt.getImage().destroy();
				}
				else if(attr.type.compareToIgnoreCase("unicode") == 0){
					UnicodeFont fnt = (UnicodeFont)node.attributes.resource;
					fnt.clearGlyphs();
					fnt.destroy();
				}
				else
					throw new SlickException("Unknown font type!");
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Animation does not exist!");
	}
	
	/**
	 * Free an Animation resource (if its' reference count is less
	 * than 0) otherwise just decrement the reference count.
	 * @param anim The identifier of the Animation to free.
	 * @see org.newdawn.slick.Animation
	 */
	public void freeAnimation(String anim)
	{
		if(animxmlmap.containsKey(anim))
		{
			ManagerNode node = animxmlmap.get(anim);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Animation does not exist!");
	}
	
	/**
	 * Free a SpriteSheet resource (if its' reference count is less
	 * than 0) otherwise just decrement the reference count.
	 * @param ss The identifier of the SpriteSheet to free.
	 * @see org.newdawn.slick.SpriteSheet
	 * @throws SlickException
	 */
	public void freeSpriteSheet(String ss) throws SlickException
	{
		if(ssxmlmap.containsKey(ss))
		{
			ManagerNode node = ssxmlmap.get(ss);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				SpriteSheet sheet = (SpriteSheet)node.attributes.resource;
				sheet.destroy();
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Animation does not exist!");
	}


	/**
	 * Free an Sound resource (if its' reference count is less
	 * than 0) otherwise just decrement the reference count.
	 * @param sound The identifier of the Sound to free.
	 * @see org.newdawn.slick.Sound
	 */
	public void freeSound(String sound)
	{
		if(soundxmlmap.containsKey(sound))
		{
			ManagerNode node = soundxmlmap.get(sound);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				Sound snd = (Sound)node.attributes.resource;
				snd.release();
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Sound does not exist!");
	}
	
	
	/**
	 * Free an Image resource (if its' reference count is less
	 * than 0) otherwise just decrement the reference count.
	 * @param image The identifier of the Image to free.
	 * @see org.newdawn.slick.Image
	 * @throws SlickException
	 */
	public void freeImage(String image) throws SlickException
	{
		if(imagexmlmap.containsKey(image))
		{
			ManagerNode node = imagexmlmap.get(image);
			node.ref_count--;
			
			if(node.ref_count <= 0)
			{
				Image img = (Image)node.attributes.resource;
				img.destroy();
				node.attributes.resource = null;
			}
		}
		else
			throw new NoSuchResourceException("Image does not exist!");
	}
	
	/**
	 * Retrieve an Animation from the ResourceManager.
	 * @param anim The name/identifier for the Animation
	 * resource being requested.
	 * @return The Animation attached to the anim specifier.
	 * @throws SlickException
	 * @see org.newdawn.slick.Animation
	 */
	public Animation getAnimation(String anim) throws SlickException
	{
		if(animxmlmap.containsKey(anim))
		{
			ManagerNode node = animxmlmap.get(anim);
			AnimationAttribs attr = (AnimationAttribs)node.attributes;
			if(attr.resource == null)
			{
				SpriteSheet sheet = null;
				SpriteSheetAttribs sattr = null;
				try {
					sheet = getSpriteSheet(attr.spritesheet_name);
					sattr = (SpriteSheetAttribs)ssxmlmap.get(attr.spritesheet_name).attributes;
				} catch(SlickException e)
				{
					Log.error("Animation :: " + anim +
							  "references a non-existant spritesheet!");
					throw e;
				}
				
				attr.resource = new Animation(sheet, attr.sx_frame, attr.sy_frame, 
													 attr.fx_frame, attr.fy_frame,
													 sattr.horizontal, attr.duration, true);
			}
			
			node.ref_count++;
			return (Animation)attr.resource;
		}
		else
			throw new NoSuchResourceException("Animation does not exist!");
	}
	
	/**
	 * Retrieve a Font from the ResourceManager.
	 * @param font The name/identifier for the Font
	 * resource being requested.
	 * @return The Font resource attached to the font
	 * specifier.
	 * @throws SlickException
	 * @see org.newdawn.slick.Font
	 */
	public Font getFont(String font) throws SlickException
	{
		if(fontxmlmap.containsKey(font))
		{
			ManagerNode node = fontxmlmap.get(font);
			FontAttribs attr = (FontAttribs)node.attributes;
			
			if(attr.resource == null)
			{
				try {
					if(attr.type.compareToIgnoreCase("angelcode") == 0)
						attr.resource = new AngelCodeFont(attr.fontfile, 
														  attr.location, 
														  false);
					else if(attr.type.compareToIgnoreCase("unicode") == 0)
						attr.resource = new UnicodeFont(attr.fontfile,
														  attr.location);
					else
						throw new SlickException("Invalid font!");
				} catch (SlickException e) {
					Log.error("Resource :: " + font + "does not exist!");
					throw e;
				}
			}
			
			node.ref_count++;
			return (Font)attr.resource;
		}
			throw new NoSuchResourceException("Font does not exist!");
	}
	
	/**
	 * Retrieve an Image resource from the ResourceManager.
	 * @param img The name/identifier for the Image resource being
	 * requested.
	 * @return The Image attached to the img specifier.
	 * @throws SlickException
	 * @see org.newdawn.slick.Image
	 */
	public Image getImage(String img) throws SlickException 
	{
		if(imagexmlmap.containsKey(img))
		{
			ManagerNode node = imagexmlmap.get(img);
			ImageAttribs attr = (ImageAttribs)node.attributes;
			
			if(attr.resource == null)
			{
				if(attr.spritesheet_name.length() > 0 &&
				   attr.spritesheet_name.compareToIgnoreCase(NULL_ATTR) != 0)
				{
					SpriteSheet ss = getSpriteSheet(attr.spritesheet_name);
					attr.resource = ss.getSubImage(attr.locx, attr.locy);
				}
				else
				{
					try {
						attr.resource = new Image(attr.location);
					}
					catch(SlickException e)
					{
						Log.error("Failed to load :: " + img +
								"! Check location!");
						throw e;
					}
				}
			}
			
			node.ref_count++;
			return (Image)attr.resource;
		}
		else
			throw new NoSuchResourceException("Image does not exist!");
	}
	
	/**
	 * Retrieve a Sound from the ResourceManager.
	 * @param sound The name/identifier for the Sound
	 * resource being requested.
	 * @return The Sound attached to the sound specifier.
	 * @throws SlickException
	 * @see org.newdawn.slick.Sound
	 */
	public Sound getSound(String sound) throws SlickException 
	{
		if(soundxmlmap.containsKey(sound))
		{
			ManagerNode node = soundxmlmap.get(sound);
			SoundAttribs attr = (SoundAttribs)node.attributes;
			if(attr.resource == null)
			{
				try {
					attr.resource = new Sound(attr.location);
				} catch (SlickException e) {
					Log.error("Resource :: " + sound + "does not exist!");
					throw e;
				}
			}
			
			node.ref_count++;
			return (Sound)attr.resource;
		}
			throw new NoSuchResourceException("Sound does not exist!");
	}
	
	/**
	 * Retrieve the specified Sprite resource from the ResourceManager.
	 * @param sprite The identifier of the Sprite to retrieve.
	 * @return The Sprite that is associated with the specified identifier.
	 * @throws SlickException
	 * @see csab.core.Sprite
	 */
	public Sprite getSprite(String sprite) throws SlickException
	{
		if(spritexmlmap.containsKey(sprite.toLowerCase()))
		{
			ManagerNode node = spritexmlmap.get(sprite.toLowerCase());
			SpriteAttribs attr = (SpriteAttribs) node.attributes;
			if(attr.resource == null)
			{
				SpriteFactory factory;
				
				try {
					factory = new SpriteFactory(attr.health, attr.killable, attr.spritename, attr.type);
				} catch (ClassNotFoundException e) {
					Log.error("Sprite factory could not find type!");
					throw new SlickException(e.getMessage());
				}
				
				attr.resource = factory;
			}
			
			Sprite spr = ((SpriteFactory)attr.resource).createNewSprite();
			node.ref_count++;
			return spr;
		}
		else
			throw new NoSuchResourceException("Sprite does not exist!");
	}
	
	/**
	 * Retrieve a SpriteSheet from the ResourceManager.
	 * @param sheet The name/identifier for the SpriteSheet
	 * resource being requested.
	 * @return The SpriteSheet resource attached to the sheet
	 * specifier.
	 * @throws SlickException
	 * @see org.newdawn.slick.SpriteSheet
	 */
	public SpriteSheet getSpriteSheet(String sheet) throws SlickException
	{
		if(ssxmlmap.containsKey(sheet))
		{
			ManagerNode node = ssxmlmap.get(sheet);
			SpriteSheetAttribs attr = (SpriteSheetAttribs)node.attributes;
			if(attr.resource == null)
			{
				try {
					attr.resource = new SpriteSheet(attr.location,
													attr.frame_w,
													attr.frame_h);
				} catch (SlickException e) {
					Log.error("Resource :: " + sheet + " does not exist!");
					throw e;
				}
			}
			
			node.ref_count++;
			return (SpriteSheet)attr.resource;
		}
			throw new NoSuchResourceException("Spritesheet does not exist!");
	}
	
	private void initAnimation(XMLElement e) throws SlickException
	{
		XMLElementList anims = e.getChildrenByName(ANIM_ELEMENTNAME);
		animxmlmap = new HashMap<String, ManagerNode>(anims.size() + 1);
		for(int i = 0; i < anims.size(); i++)
		{
			XMLElement anim = anims.get(i);
			AnimationAttribs val = new AnimationAttribs();
			val.name = anim.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			
			val.location = anim.getAttribute(REFERENCE_ATTR, NULL_ATTR);
			val.spritesheet_name = anim.getAttribute(ANIM_SPRSHEETNAME_ATTR, NULL_ATTR);
			
			//The spritesheet is invalid
			if((val.spritesheet_name.length() == 0 ||
			    val.spritesheet_name.compareToIgnoreCase(NULL_ATTR) == 0))
				throw new SlickException("Must specify either a spritesheet or location for "+
						  				 val.name);
			
			val.sx_frame = anim.getIntAttribute(ANIM_SFRAMESX_ATTR, -1);
			val.sy_frame = anim.getIntAttribute(ANIM_SFRAMESY_ATTR, -1);
			val.fx_frame = anim.getIntAttribute(ANIM_FFRAMESX_ATTR, -1);
			val.fy_frame = anim.getIntAttribute(ANIM_FFRAMESY_ATTR, -1);
			val.duration = anim.getIntAttribute(ANIM_DURATION_ATTR, 125);
			
			//Check other parameters
			if(((val.fx_frame < 0) || val.sx_frame < 0 ||
			    	val.fy_frame < 0 || val.sy_frame < 0) ||
			    	val.duration < 0)
				throw new SlickException("Bad frame value @ " + val.name);
				
			
			if(animxmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				animxmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initFont(XMLElement e) throws SlickException
	{
		XMLElementList fonts = e.getChildrenByName(FONT_ELEMENTNAME);
		fontxmlmap = new HashMap<String, ManagerNode>(fonts.size() + 1);
		for(int i = 0; i < fonts.size(); i++)
		{
			XMLElement font = fonts.get(i);
			FontAttribs val = new FontAttribs();
			val.name = font.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			
			val.location = font.getAttribute(REFERENCE_ATTR, NULL_ATTR);
			
			//Invalid or empty location
			if(val.location.length() == 0 || 
			   val.location.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a valid location for " + val.name);
					
			val.type = font.getAttribute(FONT_TYPE_ATTR, NULL_ATTR);
			
			//Invalid font type
			if(val.type.length() == 0 ||
			   val.type.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a font type for " + 
										 val.name);
			
			val.fontfile = font.getAttribute(FONT_FFILE_ATTR, NULL_ATTR);
			
			//Invalid font file location
			if(val.type.length() == 0 ||
			   val.type.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must valid font location for " + 
										 val.name);
			
			if(fontxmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				fontxmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initImage(XMLElement e) throws SlickException
	{
		XMLElementList images = e.getChildrenByName(IMAGE_ELEMENTNAME);
		imagexmlmap = new HashMap<String, ManagerNode>(images.size() + 1);
		for(int i = 0; i < images.size(); i++)
		{
			XMLElement image = images.get(i);
			ImageAttribs val = new ImageAttribs();
			val.name = image.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			
			val.location = image.getAttribute(REFERENCE_ATTR, NULL_ATTR);
			val.spritesheet_name = image.getAttribute(IMAGE_SPRSHEETNAME_ATTR, NULL_ATTR);
			
			//Both the location and spritesheet are null
			if((val.location.length() == 0 ||
					val.location.compareToIgnoreCase(NULL_ATTR) == 0)&&
			   (val.spritesheet_name.length() == 0 ||
			     	val.spritesheet_name.compareToIgnoreCase(NULL_ATTR) == 0))
						throw new SlickException("Must specify either a spritesheet or location for "+
												  val.name);
			
			//Used for referencing a spritesheet frame
			val.locx = image.getIntAttribute(IMAGE_LOCX_ATTR, 0);
			val.locy = image.getIntAttribute(IMAGE_LOCY_ATTR, 0);
			
			//locx and locy are invalid
			if(val.locx < 0 || val.locy < 0)
				throw new SlickException("Must specify a valid y and/or x location in" +
										 val.name);
			
			
			if(imagexmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				imagexmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initSound(XMLElement e) throws SlickException
	{
		XMLElementList sounds = e.getChildrenByName(SOUND_ELEMENTNAME);
		soundxmlmap = new HashMap<String, ManagerNode>(sounds.size() + 1);
		for(int i = 0; i < sounds.size(); i++)
		{
			XMLElement sound = sounds.get(i);
			SoundAttribs val = new SoundAttribs();
			val.name = sound.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			
			val.location = sound.getAttribute(REFERENCE_ATTR, NULL_ATTR);
			
			//Invalid or empty location
			if(val.location.length() == 0 || 
			   val.location.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a valid location for " + val.name);
			
			if(soundxmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				soundxmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initSprite(XMLElement e) throws SlickException
	{
		XMLElementList sprites = e.getChildrenByName(SPRITE_ELEMENTNAME);
		spritexmlmap = new HashMap<String, ManagerNode>(sprites.size() + 1);
		for(int i = 0; i < sprites.size(); i++)
		{
			XMLElement sprite = sprites.get(i);
			SpriteAttribs val = new SpriteAttribs();
			val.name = sprite.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			val.health = sprite.getIntAttribute(SPRITE_HEALTH_ATTR, 100);
			
			if(val.health <= 0)
				throw new SlickException("Sprite must have a valid health value");
			
			val.killable = sprite.getBooleanAttribute(SPRITE_KILLABLE_ATTR, true);
			
			val.type = sprite.getAttribute(SPRITE_TYPE_ATTR, NULL_ATTR);
			
			val.spritename = sprite.getAttribute(SPRITE_SPRNAME_ATTR, "");
			
			//This type is invalid or non-existent
			if(val.type.length() == 0 ||
			   val.type.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a valid type for this Sprite!");
			
			if(spritexmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				spritexmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initSpriteSheet(XMLElement e) throws SlickException
	{
		XMLElementList spritesheets = e.getChildrenByName(SS_ELEMENTNAME);
		ssxmlmap = new HashMap<String, ManagerNode>(spritesheets.size() + 1);
		for(int i = 0; i < spritesheets.size(); i++)
		{
			XMLElement spritesheet = spritesheets.get(i);
			SpriteSheetAttribs val = new SpriteSheetAttribs();
			val.name = spritesheet.getAttribute(NAME_ATTR, NULL_ATTR).toLowerCase();
			
			//Name not specified
			if(val.name.length() == 0 ||
			   val.name.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a name for this element!");
			
			val.location = spritesheet.getAttribute(REFERENCE_ATTR, NULL_ATTR);
			
			//Empty or invalid location
			if(val.location.length() == 0 || 
			   val.location.compareToIgnoreCase(NULL_ATTR) == 0)
				throw new SlickException("Must specify a valid location for " + val.name);
			
			val.frame_w = spritesheet.getIntAttribute(SS_FRAMEW_ATTR, 0);
			val.frame_h = spritesheet.getIntAttribute(SS_FRAMEH_ATTR, 0);
			val.horizontal = spritesheet.getBooleanAttribute(SS_HORIZONTAL_ATTR, false);
			
			//Frame_w or Frame_h are invalid
			if(val.frame_h <= 0 || val.frame_w <= 0)
				throw new SlickException("You must specify a valid frame_w and/or frame_h!");
			
			if(ssxmlmap.containsKey(val.name))
				Log.warn("Two instances of " + val.name + " in " + this.rscxmlname);
			else
				ssxmlmap.put(val.name, new ManagerNode(val));
		}
	}
	
	private void initXML(XMLElementList el) throws SlickException
	{
		for(int i = 0; i < el.size(); i++)
		{
			XMLElement e = el.get(i);
			if(e.getName().compareToIgnoreCase("animations") == 0)
				initAnimation(e);
			else if(e.getName().compareToIgnoreCase("images") == 0)
				initImage(e);
			else if(e.getName().compareToIgnoreCase("sounds") == 0)
				initSound(e);
			else if(e.getName().compareToIgnoreCase("sprites") == 0)
				initSprite(e);
			else if(e.getName().compareToIgnoreCase("fonts") == 0)
				initFont(e);
			else if(e.getName().compareToIgnoreCase("spritesheets") == 0)
				initSpriteSheet(e);
			else
				Log.warn("Non-standard node \"" + e.getName() +
						"\" in " + 
						rscxmlname + "!");
		}
	}
	
	
	/**
	 * An Exception which indicates that a resource does not exist
	 * in the ResourceManager's resource list.
	 * @author David Young
	 */
	@SuppressWarnings("serial")
	public class NoSuchResourceException extends RuntimeException {
		/**
		 * The constructor for this exception.
		 * @param msg Standard Exception message parameter.
		 * @see java.lang.Exception
		 */
		public NoSuchResourceException(String msg)
		{
			super(msg);
		}
	}
	
	private class AnimationAttribs extends NodeAttribs {
		public int duration;
		public int fx_frame;
		public int fy_frame;
		public String spritesheet_name;
		public int sx_frame;
		public int sy_frame;
	}
	
	private class FontAttribs extends NodeAttribs {
		public String fontfile;
		public String type;
	}
	
	private class ImageAttribs extends NodeAttribs {
		public int locx;
		public int locy;
		public String spritesheet_name;
	}
	
	private class ManagerNode {
		public NodeAttribs attributes;
		public int ref_count;
		public ManagerNode(NodeAttribs attr)
		{
			this.attributes = attr;
			ref_count = 0;
		}
	}
	
	private class NodeAttribs { 
		public String location;
		public String name;
		public Object resource;
		@SuppressWarnings("unused")
		public void NodeAtribs()
		{
			resource = null;
		}
	}
	
	private class SoundAttribs extends NodeAttribs {}
	
	private class SpriteAttribs extends NodeAttribs {
		public int health;
		public boolean killable;
		public String spritename;
		public String type;
	}
	
	private class SpriteSheetAttribs extends NodeAttribs {
		public int frame_h;
		public int frame_w;
		public boolean horizontal;
	}
}